/*
 * Copyright 2017 Rainbow.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.seari.semodbus.server;

import com.ghgande.j2mod.modbus.ModbusIOException;
import com.ghgande.j2mod.modbus.io.ModbusTCPTransport;
import com.ghgande.j2mod.modbus.msg.ModbusRequest;
import com.ghgande.j2mod.modbus.net.AbstractModbusListener;
import com.ghgande.j2mod.modbus.util.ModbusUtil;
import java.io.EOFException;
import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Rainbow
 */
public class SEmodbusTCPTransport extends ModbusTCPTransport
{
    private static final Logger logger = LoggerFactory.getLogger(SEmodbusTCPTransport.class);

    public SEmodbusTCPTransport(Socket socket)
    {
        super(socket);
    }
    
    @Override
    public ModbusRequest readRequest(AbstractModbusListener listener) throws ModbusIOException {

        ModbusRequest req;
        try {
            byteInputStream.reset();

            synchronized (byteInputStream) {
                byte[] buffer = byteInputStream.getBuffer();

                if (!headless) {
                    dataInputStream.readFully(buffer, 0, 6);

                    // The transaction ID must be treated as an unsigned short in
                    // order for validation to work correctly.

                    int transaction = ModbusUtil.registerToShort(buffer, 0) & 0x0000FFFF;
                    int protocol = ModbusUtil.registerToShort(buffer, 2);
                    int count = ModbusUtil.registerToShort(buffer, 4);

                    dataInputStream.readFully(buffer, 6, count);

                    if (logger.isDebugEnabled()) {
                        logger.debug("Read: {}", ModbusUtil.toHex(buffer, 0, count + 6));
                    }

                    byteInputStream.reset(buffer, (6 + count));
                    byteInputStream.skip(6);

                    int unit = byteInputStream.readByte();
                    int functionCode = byteInputStream.readUnsignedByte();

                    byteInputStream.reset();
                    req = SEmodbusRequest.createModbusRequest(functionCode);
                    req.setUnitID(unit);
                    req.setHeadless(false);

                    req.setTransactionID(transaction);
                    req.setProtocolID(protocol);
                    req.setDataLength(count);

                    req.readFrom(byteInputStream);
                }
                else {

                    // This is a headless request.

                    int unit = dataInputStream.readByte();
                    int function = dataInputStream.readByte();

                    req = SEmodbusRequest.createModbusRequest(function);
                    req.setUnitID(unit);
                    req.setHeadless(true);
                    req.readData(dataInputStream);

                    // Discard the CRC. This is a TCP/IP connection, which has
                    // proper error correction and recovery.

                    dataInputStream.readShort();
                    if (logger.isDebugEnabled()) {
                        logger.debug("Read: {}", req.getHexMessage());
                    }
                }
            }
            return req;
        }
        catch (EOFException eoex) {
            throw new ModbusIOException("End of File", true);
        }
        catch (SocketTimeoutException x) {
            throw new ModbusIOException("Timeout reading request", x);
        }
        catch (SocketException sockex) {
            throw new ModbusIOException("Socket Exception", sockex);
        }
        catch (IOException ex) {
            throw new ModbusIOException("I/O exception - failed to read", ex);
        }
    }
}
